/***
* Feel free to use this code however you wish.
* Created by Alie Tan (www(dot)alietan(dot)com)
*/

#include <ma.h>
#include <mastdlib.h>
#include <MAUtil/Moblet.h>

#include <MoBox2D/Box2D.h>

#include "Sprite.h"
#include "MAHeaders.h"

#define SCALE 1.0
#define TIMER_PERIOD 1

// Ahhh!, nothing like dangerous, multi-valued stuff...  But it's handy!
#define SCREEN_TO_PHYS(x, y) (x)/SCALE, (y)/SCALE
#define PHYS_TO_SCREEN(x, y) (x)*SCALE, (y)*SCALE

using namespace MAUtil;


class MyMoblet : public Moblet, TimerListener
{
public:
	MyMoblet()
	{
		// Get screen dimensions.
		MAExtent size = maGetScrSize();
		// Extract the screen width
		screenWidth = EXTENT_X(size);
		// Extract the screen height
		screenHeight = EXTENT_Y(size);

		// Create the image in which the cube will be drawn, to draw multiple versions of it.
		maCreateDrawableImage(1, screenWidth, screenHeight);

		initWorld();
		initPendulum();
		addTimer(this, TIMER_PERIOD, 0);
	}

	~MyMoblet()
	{
	    delete world;
	    delete ballSprite;
	    delete paddleSprite;
	}

public:
	void loadTexture()
	{
		{
		MAExtent textureSize = maGetImageSize(R_BALL);
		int width = EXTENT_X(textureSize);
		int height = EXTENT_Y(textureSize);
		unsigned int *texture = new unsigned int [width*height];
		MARect srcRect = {0, 0, width, height};
		maGetImageData(R_BALL, texture, &srcRect, width);
		ballSprite = new CSprite(texture, width, height);
		}

		{
		MAExtent textureSize = maGetImageSize(R_PADDLE);
		int width = EXTENT_X(textureSize);
		int height = EXTENT_Y(textureSize);
		unsigned int *texture = new unsigned int [width*height];
		MARect srcRect = {0, 0, width, height};
		maGetImageData(R_PADDLE, texture, &srcRect, width);
		paddleSprite = new CSprite(texture, width, height);
		}
	}

	void initPendulum()
	{
		loadTexture();

		int x = screenWidth/2;

		// ceiling
		b2BodyDef bodyDef;
		bodyDef.position.Set(x, 20);
		bodyDef.userData = paddleSprite;
		paddleBody = world->CreateBody(&bodyDef);

		b2PolygonShape polygonShape;
		polygonShape.SetAsBox(60.0f, 20.0f);

		b2FixtureDef fixtureDef;
		fixtureDef.shape = &polygonShape;
		fixtureDef.density = 1.0f;
		fixtureDef.friction = 0.2f;
		fixtureDef.restitution = 0.2f;
		paddleBody->CreateFixture(&fixtureDef);

		anchor1 = paddleBody->GetPosition()+b2Vec2(20, 0);
		anchor2 = paddleBody->GetPosition()-b2Vec2(20, 0);

		// Create ball body and shape
		b2BodyDef ballBodyDef;
		ballBodyDef.type = b2_dynamicBody;
		ballBodyDef.position.Set(screenWidth/2+20, 150);
		ballBodyDef.userData = ballSprite;

		b2CircleShape circle;
		circle.m_radius = (ballSprite->getWidth()/2)/SCALE;

		b2FixtureDef ballShapeDef;
		ballShapeDef.shape = &circle;
		ballShapeDef.density = 100.0f;
		ballShapeDef.friction = 0.5f;
		ballShapeDef.restitution = 0.2f;

		circleBody = world->CreateBody(&ballBodyDef);
		circleBody->CreateFixture(&ballShapeDef);

		b2DistanceJointDef jointDef;
		jointDef.Initialize(paddleBody, circleBody, anchor1, circleBody->GetPosition());
		jointDef.collideConnected = true;
		b2DistanceJoint *joint = (b2DistanceJoint*) world->CreateJoint(&jointDef);

		ballBodyDef.position.Set(screenWidth/2-20, 150);
		ballBodyDef.userData = ballSprite;

		circleBody2 = world->CreateBody(&ballBodyDef);
		circleBody2->CreateFixture(&ballShapeDef);

		jointDef.Initialize(paddleBody, circleBody2, anchor2, circleBody2->GetPosition());
		jointDef.collideConnected = true;
		joint = (b2DistanceJoint*) world->CreateJoint(&jointDef);
	}

	void initWorld()
	{
		// Create a world
		b2Vec2 gravity = b2Vec2(0.0f, 10.0f);
		bool doSleep = true;
		world = new b2World(gravity, doSleep);

		// Create edges around the entire screen
		b2BodyDef groundBodyDef;
		b2Body *groundBody = world->CreateBody(&groundBodyDef);
		b2PolygonShape groundBox;
		b2FixtureDef boxShapeDef;
		boxShapeDef.shape = &groundBox;
		groundBox.SetAsEdge(b2Vec2(-screenWidth,0), b2Vec2(SCREEN_TO_PHYS(screenWidth*2, 0)));
		groundBody->CreateFixture(&boxShapeDef);
	}

	void runTimerEvent()
	{
	    // Now sim code
		timeStep = 1.0f / 60.0f;

		// Set the following draw calls to draw to the drawable image created (draw target 1).
    	maSetDrawTarget(1);

    	maSetColor(0x0088ff);
		maFillRect(0, 0, screenWidth, screenHeight);

		world->Step(timeStep, 1, 1);

    	for(b2Body *b = world->GetBodyList(); b; b=b->GetNext())
		{
			if (b->GetUserData() != NULL)
			{
				CSprite *sprite = (CSprite *)b->GetUserData();
				int width = sprite->getWidth();
				int height = sprite->getHeight();
				int x = (int)(b->GetPosition().x*SCALE - width/2);
				int y = (int)(b->GetPosition().y*SCALE - height/2);
				float32 angle = b->GetAngle();
				void *texture = sprite->getData();

				// debug draw
				if(circleBody == b)
				{
					maSetColor(0xaa8888);
					maLine((int)(x)+width/2, (int)(y)+height/2, (int)(anchor1.x), (int)(anchor1.y));
				}
				else if(circleBody2 == b)
				{
					maSetColor(0xaa8888);
					maLine((int)(x)+width/2, (int)(y)+height/2, (int)(anchor2.x), (int)(anchor2.y));
				}

				MARect srcRect = {0, 0, width, height};
				MAPoint2d dstPoint = {x, y};
				maDrawRGB(&dstPoint, texture, &srcRect, width);
			}
		}

		// Set the following draw calls to draw to the screen (draw target 0).
		maSetDrawTarget(0);

		MARect srcRect = {0, 0, screenWidth, screenHeight};
		MAPoint2d dstPoint = {0, 0};
		maDrawImageRegion(1, &srcRect, &dstPoint, TRANS_NONE);

		/// Updates the screen
		maUpdateScreen();

		/// Keep the backlight alive.
		maResetBacklight();
	}

	b2Body *getBodyAtMouse(b2Vec2 p)
	{
		b2Body *body = NULL;
		bool finish = false;
		for(b2Body *b = world->GetBodyList(); b&&!finish; b=b->GetNext())
		{
			if(body != paddleBody)
			{
				for (b2Fixture* f = b->GetFixtureList(); f; f = f->GetNext())
				{
					if(f->TestPoint(p))
					{
						body = b;
						finish = true;
						break;
					}
				}
			}
		}
		return body;
	}

	void pointerPressEvent(MAPoint2d p)
	{
		b2Body *body = getBodyAtMouse(b2Vec2(p.x, p.y));
	    if (body)
	    {
			b2MouseJointDef mouseJointDef;
			mouseJointDef.bodyA = paddleBody;
			mouseJointDef.bodyB = body;
			mouseJointDef.target.Set(p.x, p.y);
			mouseJointDef.maxForce = 1000.0f * body->GetMass();
			mouseJointDef.dampingRatio = 0.0f;
			mouseJointDef.frequencyHz = 100.0f;
			mouseJoint = (b2MouseJoint *)world->CreateJoint(&mouseJointDef);
			body->SetAwake(true);
	    }
	}

	void pointerMoveEvent(MAPoint2d p)
	{
		pointerPos = b2Vec2(p.x, p.y);
		if(mouseJoint)
		{
			mouseJoint->SetTarget(pointerPos);
		}
	}

	void pointerReleaseEvent(MAPoint2d p)
	{
		if (mouseJoint)
		{
			world->DestroyJoint(mouseJoint);
			mouseJoint = NULL;
		}
	}

private:
	int screenWidth;
	int screenHeight;

	float32 timeStep;

	b2Vec2 anchor1;
	b2Vec2 anchor2;

	b2Vec2 pointerPos;

	b2World *world;
	b2Body *paddleBody;
	b2Body *circleBody;
	b2Body *circleBody2;
	b2MouseJoint *mouseJoint;

	CSprite *ballSprite;
	CSprite *paddleSprite;
};

extern "C" int MAMain()
{
	Moblet::run(new MyMoblet());
}
